package com.derbysoft.dswitch.adapter.ihg.webservice.impl;

import com.derbysoft.dswitch.adapter.ihg.common.domain.Direction;
import com.derbysoft.dswitch.adapter.ihg.common.log.LogDetailPair;
import com.derbysoft.dswitch.adapter.ihg.common.log.TransactionLogger;
import com.derbysoft.dswitch.adapter.ihg.common.utils.ExecutorUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.input.CloseShieldInputStream;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.*;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.AuthState;
import org.apache.http.auth.Credentials;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.params.CookiePolicy;
import org.apache.http.client.protocol.ClientContext;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.auth.BasicScheme;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;

import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import java.io.*;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.Executor;

/**
 * Created by: jason
 * Date: 2012-06-08
 */
public abstract class AbstractProviderWebService<RQ, RS> {

    private static final Log LOGGER = LogFactory.getLog(AbstractProviderWebService.class);

    private static final String ENCODING = "UTF-8";

    @Autowired
    @Qualifier("taskExecutor")
    private Executor executor;

    @SuppressWarnings("unchecked")
    protected RS invoke(Jaxb2Marshaller jaxb2Marshaller, final RQ request, AccountProvider accountProvider) throws Exception {
        HttpPost httpPost = new HttpPost(accountProvider.getWebServiceURL());
        HttpClient httpClient = createHttpClient(accountProvider);
        return send(jaxb2Marshaller, httpClient, httpPost, request);
    }

    protected List<ResponsePair<RQ, RS>> batchInvoke(Jaxb2Marshaller jaxb2Marshaller, List<RequestPair<RQ>> requests, AccountProvider accountProvider)
            throws Exception {

        Collection<Callable<ResponsePair<RQ, RS>>> taskPairs = new ArrayList<Callable<ResponsePair<RQ, RS>>>();
        for (final RequestPair<RQ> request : requests) {
            taskPairs.add(createTask(jaxb2Marshaller, accountProvider, request));
        }
        return ExecutorUtils.batchExecute(taskPairs, executor);
    }

    private DefaultHttpClient createHttpClient(AccountProvider accountProvider) {
        DefaultHttpClient client = new DefaultHttpClient();
        client.getParams().setParameter(CookiePolicy.IGNORE_COOKIES, true);
        String userName = accountProvider.getAuthorization().getUserName();
        String password = accountProvider.getAuthorization().getPassword();
        Credentials credentials = new UsernamePasswordCredentials(userName, password);
        client.getCredentialsProvider().setCredentials(AuthScope.ANY, credentials);
        client.addRequestInterceptor(createRequestInterceptor(), 0);
        client.addResponseInterceptor(createResponseInterceptor(), 0);
        return client;
    }

    private Callable<ResponsePair<RQ, RS>> createTask(final Jaxb2Marshaller jaxb2Marshaller,
                                                      final AccountProvider accountProvider, final RequestPair<RQ> request) {
        return new Callable<ResponsePair<RQ, RS>>() {
            @Override
            public ResponsePair<RQ, RS> call() throws Exception {
                HttpPost httpPost = new HttpPost(accountProvider.getWebServiceURL());
                HttpClient httpClient = createHttpClient(accountProvider);
                RS rs = send(jaxb2Marshaller, httpClient, httpPost, request.getRequest());
                return new ResponsePair<RQ, RS>(request, rs);
            }
        };
    }

    @SuppressWarnings("unchecked")
    private RS send(final Jaxb2Marshaller jaxb2Marshaller, HttpClient httpClient, HttpPost httpPost, RQ request) throws Exception {
        setRequestBody(jaxb2Marshaller, httpPost, request);

        HttpResponse httpResponse = httpClient.execute(httpPost);

        InputStream inputStream = httpResponse.getEntity().getContent();

        Set<LogDetailPair> logDetails = TransactionLogger.getLogDetails().get();
        if (logDetails == null) {
            logDetails = new HashSet<LogDetailPair>();
        }

        String response = IOUtils.toString(new CloseShieldInputStream(inputStream), ENCODING);
        logDetails.add(LogDetailPair.build(response, Direction.FROM_HOTEL));
        Object obj = jaxb2Marshaller.unmarshal(new StreamSource(new StringReader(response)));
        EntityUtils.consume(httpResponse.getEntity());
        return (RS) obj;
    }

    private <RQ> void setRequestBody(Jaxb2Marshaller jaxb2Marshaller, HttpPost httpPost, RQ rq) throws Exception {
        httpPost.setHeader("Content-type", "text/xml; charset=UTF-8");
        Writer writer = new StringWriter();
        jaxb2Marshaller.marshal(rq, new StreamResult(writer));
        String request = writer.toString();
        LOGGER.debug(request);
        httpPost.setEntity(new StringEntity(request, ContentType.create("text/xml", Consts.UTF_8)));
    }

    private HttpRequestInterceptor createRequestInterceptor() {
        return new HttpRequestInterceptor() {
            public void process(final HttpRequest request, final HttpContext context) throws HttpException, IOException {
                AuthState authState = (AuthState) context.getAttribute(ClientContext.TARGET_AUTH_STATE);
                if (authState.getAuthScheme() == null) {
                    CredentialsProvider credentialsProvider = (CredentialsProvider) context.getAttribute(
                            ClientContext.CREDS_PROVIDER);
                    Credentials credentials = credentialsProvider.getCredentials(AuthScope.ANY);
                    if (credentials != null) {
                        authState.update(new BasicScheme(), credentials);
                    }
                }
            }
        };
    }

    private static HttpResponseInterceptor createResponseInterceptor() {
        return new HttpResponseInterceptor() {
            @Override
            public void process(HttpResponse response, HttpContext context) throws HttpException, IOException {
                context.setAttribute(ClientContext.COOKIE_SPEC, null);
            }
        };
    }
}
